﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.View.Regions;
using Windows.ApplicationModel;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace StyleMVVM.View
{
	public class LayoutAwareUserControl : UserControl
	{
		private List<Control> _layoutAwareControls;

		/// <summary>
		/// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
		/// </summary>
		public LayoutAwareUserControl()
		{
			if (DesignMode.DesignModeEnabled)
			{
				return;
			}

			// When this page is part of the visual tree make two changes:
			// 1) Map application view state to visual state for the page
			// 2) Handle keyboard and mouse navigation requests
			this.Loaded += (sender, e) =>
				               {
					               this.StartLayoutUpdates(sender, e);

					               // Keyboard and mouse navigation only apply when occupying the entire window
					               if (this.ActualHeight == Window.Current.Bounds.Height &&
					                   this.ActualWidth == Window.Current.Bounds.Width)
					               {
						               // Listen to the window directly so focus isn't required
						               Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
							               CoreDispatcher_AcceleratorKeyActivated;
						               Window.Current.CoreWindow.PointerPressed +=
							               this.CoreWindow_PointerPressed;
					               }
				               };

			// Undo the same changes when the page is no longer visible
			this.Unloaded += (sender, e) =>
				                 {
					                 this.StopLayoutUpdates(sender, e);
					                 Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
						                 CoreDispatcher_AcceleratorKeyActivated;
					                 Window.Current.CoreWindow.PointerPressed -=
						                 this.CoreWindow_PointerPressed;
				                 };
		}

		#region Navigation support

		/// <summary>
		/// Invoked on every keystroke, including system keys such as Alt key combinations, when
		/// this page is active and occupies the entire window.  Used to detect keyboard navigation
		/// between pages even when the page itself doesn't have focus.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="args">Event data describing the conditions that led to the event.</param>
		private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
		                                                    AcceleratorKeyEventArgs args)
		{
			var virtualKey = args.VirtualKey;

			// Only investigate further when Left, Right, or the dedicated Previous or Next keys
			// are pressed
			if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
			     args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
			    (virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
			     (int)virtualKey == 166 || (int)virtualKey == 167))
			{
				var coreWindow = Window.Current.CoreWindow;
				var downState = CoreVirtualKeyStates.Down;
				bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
				bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
				bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
				bool noModifiers = !menuKey && !controlKey && !shiftKey;
				bool onlyAlt = menuKey && !controlKey && !shiftKey;

				if (((int)virtualKey == 166 && noModifiers) ||
				    (virtualKey == VirtualKey.Left && onlyAlt))
				{
					// When the previous key or Alt+Left are pressed navigate back
					args.Handled = true;

					IList<IRegion> regions = BaseRegion.GetCurrentRegions(Parent);

					if (regions.Count > 0)
					{
						regions[0].GoBack();
					}
				}
				else if (((int)virtualKey == 167 && noModifiers) ||
				         (virtualKey == VirtualKey.Right && onlyAlt))
				{
					// When the next key or Alt+Right are pressed navigate forward
					args.Handled = true;

					IList<IRegion> regions = BaseRegion.GetCurrentRegions(Parent);

					if (regions.Count > 0)
					{
						regions[0].GoForward();
					}
				}
			}
		}

		/// <summary>
		/// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
		/// page is active and occupies the entire window.  Used to detect browser-style next and
		/// previous mouse button clicks to navigate between pages.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="args">Event data describing the conditions that led to the event.</param>
		private void CoreWindow_PointerPressed(CoreWindow sender,
		                                       PointerEventArgs args)
		{
			var properties = args.CurrentPoint.Properties;

			// Ignore button chords with the left, right, and middle buttons
			if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
			    properties.IsMiddleButtonPressed)
			{
				return;
			}

			// If back or foward are pressed (but not both) navigate appropriately
			bool backPressed = properties.IsXButton1Pressed;
			bool forwardPressed = properties.IsXButton2Pressed;
			if (backPressed ^ forwardPressed)
			{
				args.Handled = true;
				if (backPressed)
				{
					IList<IRegion> regions = BaseRegion.GetCurrentRegions(Parent);

					if (regions.Count > 0)
					{
						regions[0].GoBack();
					}
				}
				if (forwardPressed)
				{
					IList<IRegion> regions = BaseRegion.GetCurrentRegions(Parent);

					if (regions.Count > 0)
					{
						regions[0].GoForward();
					}
				}
			}
		}

		#endregion

		#region Visual state switching

		/// <summary>
		/// Invoked as an event handler, typically on the <see cref="FrameworkElement.Loaded"/>
		/// event of a <see cref="Control"/> within the page, to indicate that the sender should
		/// start receiving visual state management changes that correspond to application view
		/// state changes.
		/// </summary>
		/// <param name="sender">Instance of <see cref="Control"/> that supports visual state
		/// management corresponding to view states.</param>
		/// <param name="e">Event data that describes how the request was made.</param>
		/// <remarks>The current view state will immediately be used to set the corresponding
		/// visual state when layout updates are requested.  A corresponding
		/// <see cref="FrameworkElement.Unloaded"/> event handler connected to
		/// <see cref="StopLayoutUpdates"/> is strongly encouraged.  Instances of
		/// <see cref="LayoutAwarePage"/> automatically invoke these handlers in their Loaded and
		/// Unloaded events.</remarks>
		/// <seealso cref="DetermineVisualState"/>
		/// <seealso cref="InvalidateVisualState"/>
		public void StartLayoutUpdates(object sender, RoutedEventArgs e)
		{
			var control = sender as Control;
			if (control == null)
			{
				return;
			}
			if (this._layoutAwareControls == null)
			{
				// Start listening to view state changes when there are controls interested in updates
				Window.Current.SizeChanged += this.WindowSizeChanged;
				this._layoutAwareControls = new List<Control>();
			}
			this._layoutAwareControls.Add(control);

			// Set the initial visual state of the control
			VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false);
		}

		private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
		{
			this.InvalidateVisualState();
		}

		/// <summary>
		/// Invoked as an event handler, typically on the <see cref="FrameworkElement.Unloaded"/>
		/// event of a <see cref="Control"/>, to indicate that the sender should start receiving
		/// visual state management changes that correspond to application view state changes.
		/// </summary>
		/// <param name="sender">Instance of <see cref="Control"/> that supports visual state
		/// management corresponding to view states.</param>
		/// <param name="e">Event data that describes how the request was made.</param>
		/// <remarks>The current view state will immediately be used to set the corresponding
		/// visual state when layout updates are requested.</remarks>
		/// <seealso cref="StartLayoutUpdates"/>
		public void StopLayoutUpdates(object sender, RoutedEventArgs e)
		{
			var control = sender as Control;
			if (control == null || this._layoutAwareControls == null)
			{
				return;
			}
			this._layoutAwareControls.Remove(control);
			if (this._layoutAwareControls.Count == 0)
			{
				// Stop listening to view state changes when no controls are interested in updates
				this._layoutAwareControls = null;
				Window.Current.SizeChanged -= this.WindowSizeChanged;
			}
		}

		/// <summary>
		/// Translates <see cref="ApplicationViewState"/> values into strings for visual state
		/// management within the page.  The default implementation uses the names of enum values.
		/// Subclasses may override this method to control the mapping scheme used.
		/// </summary>
		/// <param name="viewState">View state for which a visual state is desired.</param>
		/// <returns>Visual state name used to drive the
		/// <see cref="VisualStateManager"/></returns>
		/// <seealso cref="InvalidateVisualState"/>
		protected virtual string DetermineVisualState(ApplicationViewState viewState)
		{
			return viewState.ToString();
		}

		/// <summary>
		/// Updates all controls that are listening for visual state changes with the correct
		/// visual state.
		/// </summary>
		/// <remarks>
		/// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to
		/// signal that a different value may be returned even though the view state has not
		/// changed.
		/// </remarks>
		public void InvalidateVisualState()
		{
			if (this._layoutAwareControls != null)
			{
				string visualState = DetermineVisualState(ApplicationView.Value);
				foreach (var layoutAwareControl in this._layoutAwareControls)
				{
					VisualStateManager.GoToState(layoutAwareControl, visualState, false);
				}
			}
		}

		#endregion

		#region Process lifetime management

		private String _pageKey;

		/// <summary>
		/// Populates the page with content passed during navigation.  Any saved state is also
		/// provided when recreating a page from a prior session.
		/// </summary>
		/// <param name="navigationParameter">The parameter value passed to
		/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
		/// </param>
		/// <param name="pageState">A dictionary of state preserved by this page during an earlier
		/// session.  This will be null the first time a page is visited.</param>
		protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
		{
		}

		/// <summary>
		/// Preserves state associated with this page in case the application is suspended or the
		/// page is discarded from the navigation cache.  Values must conform to the serialization
		/// requirements of <see cref="SuspensionManager.SessionState"/>.
		/// </summary>
		/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
		protected virtual void SaveState(Dictionary<String, Object> pageState)
		{
		}

		#endregion
	}
}